/**
 * 
 */
package com.volatileengine.scene;

import javax.vecmath.Matrix3f;
import javax.vecmath.Matrix4f;
import javax.vecmath.Vector2f;
import javax.vecmath.Vector3f;

/**
 * @author darkprophet
 * 
 */
public class Frustum {

	private float fieldOfView;
	private float aspectRatio;
	private float nearPlane;
	private float farPlane;

	private Matrix4f transform;

	private Vector2f sphereFactor;
	private float tanField;

	public Frustum(float field, float aspect, float near, float far) {
		fieldOfView = field;
		aspectRatio = aspect;
		nearPlane = near;
		farPlane = far;

		transform = new Matrix4f();
		transform.setIdentity();

		recalculateSphereFactors();
	}

	private void recalculateSphereFactors() {
		sphereFactor = new Vector2f();
		tanField = (float) Math.tan(Math.toRadians(fieldOfView));
		sphereFactor.y = 1.0f / (float) Math.cos(Math.toRadians(fieldOfView));
		sphereFactor.x = 1.0f / (float) Math.atan(tanField * aspectRatio);
	}

	/**
	 * @return Returns the aspectRatio.
	 */
	public float getAspectRatio() {
		return aspectRatio;
	}

	/**
	 * @param aspectRatio
	 *             The aspectRatio to set.
	 */
	public void setAspectRatio(float aspectRatio) {
		this.aspectRatio = aspectRatio;
		recalculateSphereFactors();
	}

	/**
	 * @return Returns the farPlane.
	 */
	public float getFarPlane() {
		return farPlane;
	}

	/**
	 * @param farPlane
	 *             The farPlane to set.
	 */
	public void setFarPlane(float farPlane) {
		this.farPlane = farPlane;
	}

	/**
	 * @return Returns the fieldOfView.
	 */
	public float getFieldOfView() {
		return fieldOfView;
	}

	/**
	 * @param fieldOfView
	 *             The fieldOfView to set.
	 */
	public void setFieldOfView(float fieldOfView) {
		this.fieldOfView = fieldOfView;
		recalculateSphereFactors();
	}

	/**
	 * @return Returns the nearPlane.
	 */
	public float getNearPlane() {
		return nearPlane;
	}

	/**
	 * @param nearPlane
	 *             The nearPlane to set.
	 */
	public void setNearPlane(float nearPlane) {
		this.nearPlane = nearPlane;
	}

	/**
	 * Get the transform for the frustum
	 * 
	 * @return
	 */
	public Matrix4f getTransform() {
		return transform;
	}

	// scratch matrix used in frustum calculations
	private Matrix3f scratchMat = new Matrix3f();

	/**
	 * Calculate whether the container is inside or outside the frustum using
	 * the Radar approach
	 * 
	 * @param container
	 * @return 0 if INTERSECTS, 1 if completely INSIDE, and -1 if completely
	 *         OUTISIDE
	 */
	public int checkContainer(Container container) {
		transform.getRotationScale(scratchMat);
		// get the position of the frustum
		Vector3f pos = new Vector3f();
		pos.x = transform.m03;
		pos.y = transform.m13;
		pos.z = transform.m23;

		// get the RIGHT vector for the frustum
		Vector3f right = new Vector3f();
		scratchMat.getColumn(0, right);

		// get the UP vector for the frustum
		Vector3f up = new Vector3f();
		scratchMat.getColumn(1, up);

		// get the DIR vector for the frustum
		Vector3f dir = new Vector3f();
		scratchMat.getColumn(2, dir);

		// find V
		Vector3f v = new Vector3f();
		container.getCentre().sub(pos, v);

		// default to inside
		int result = 1;

		// dot Z
		float z = dir.dot(v);
		if (z > farPlane + container.getRadius() || z < nearPlane - container.getRadius()) {
			// OUTSIDE
			return 2;
		} else if (z > farPlane - container.getRadius() || z < nearPlane + container.getRadius()) {
			// intersect on Z
			result = 3;
		}

		// dot UP
		float y = up.dot(v);
		z *= tanField;
		float d = sphereFactor.y * container.getRadius();
		if (y > z + d || y < z - d) {
			return 2;
		} else if (y > z - d || y < z + d) {
			result = 3;
		}

		// dot RIGHT
		float x = right.dot(v);
		z *= aspectRatio;
		d = sphereFactor.x * container.getRadius();
		if (x > z + d || x < z - d) {
			return 2;
		} else if (x > z - d || x < z + d) {
			result = 3;
		}

		return result;
	}
}
